package com.teufandsub.socialproximity.controller;

import java.util.AbstractMap.SimpleEntry;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import org.apache.http.message.BasicNameValuePair;
import org.json.JSONObject;

import android.app.ActivityManager;
import android.app.ActivityManager.RunningTaskInfo;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.AsyncTask;
import android.util.Log;
import android.widget.Toast;

import com.teufandsub.socialproximity.R;
import com.teufandsub.socialproximity.gui.ChattingActivity;
import com.teufandsub.socialproximity.gui.LoginActivity;
import com.teufandsub.socialproximity.logic.AuthenticationInfo;
import com.teufandsub.socialproximity.logic.ChatConversation;
import com.teufandsub.socialproximity.logic.Friend;
import com.teufandsub.socialproximity.logic.Message;
import com.teufandsub.socialproximity.logic.RegistrationInfo;
import com.teufandsub.socialproximity.logic.User;
import com.teufandsub.socialproximity.services.HttpService;

/**
 * @author Bastien Mathias <bastienmathias@gmail.com>
 * @author Antoine Teuf <antoine.teuf@gmail.com>
 * @version 1.0
 * @since 2013-05-19
 * 
 * The Controller Class to link the logic layer and the view layer.
 */

public class Controller {
	
	//ACRA.getErrorReporter().handleException(e);

	private static final Controller controller=new Controller();
	private ArrayList<Friend> friends = new ArrayList<Friend>();
	private ArrayList<ChatConversation> alConversations = new ArrayList<ChatConversation>();
	private int notifId = 0;
	private String regId;                                             
	
	/**
	 * Since Controller is a Singleton, this method is used to send
	 * the instance of the controller.
	 * 
	 * @return Controller Return the instance of the Controller.
	 */
	public static Controller getInstance(){
		return controller;
	}
	
	/**
	 * This method is used to retrieve all friend found after a research.
	 * 
	 * @return ArrayList<Friend> The list of friend found.
 	 */
	public ArrayList<Friend> getResultList(){
		return friends;
	}
	
	/**
	 * This method is used to set the list of users found after a research
	 * Used to set the adapter for the ListView which display friends
	 * 
	 * @param list ArrayList<Friend> The list of friend after a research.
	 */
	public void setConnectedUserList(ArrayList<Friend> list){
		friends=list;
	}
	
	/**
	 * Method used to know if we have to create a new conversation or not.
	 * 
	 * @param friend User The user you want to know if you already have a conversation with.
	 * @return boolean Return true if you haven't talk with this user yet, otherwise return false.
	 */
	public boolean isNewConversation(User friend){
		for(ChatConversation convers : alConversations){
			if(convers.getFriend().equals(friend)){
				return false;
			}
		}
		return true;
	}
	
	/**
	 * Method used if there is no conversation set with the user in parameter.
	 * Used to store this conversation.
	 * 
	 * @param user User The user which you want to create a new Conversation. 
	 * @return ChatConversation Return the new ChatConversation object created.
	 */
	public ChatConversation newConversation(User user){
		ChatConversation convers = new ChatConversation(user);
		alConversations.add(convers);
		return convers;
	}
	
	/**
	 * Method used to get the conversation you had with a user.
	 * Using the as parameter.
	 * 
	 * @param user User The user for which you want to retrieve the conversation.
	 * @return ChatConversation The ChatConversion of the user in parameter.
	 */
	public ChatConversation getConversationByUser(User user){
		Iterator<ChatConversation> it = alConversations.iterator();
		ChatConversation convers=it.next();
		
		while(it.hasNext() && !convers.getFriend().equals(user)){
			convers=it.next();
		}
		return convers;
	}

	/**
	 * Method used to retrieve all the conversation you had with users.
	 * 
	 * @return ArrayList<ChatConversation> Return all the conversation you already had.
	 */
	public ArrayList<ChatConversation> getAlConversations() {
		return alConversations;
	}
	
	/**
	 * This Method is used to know which view is display to the screen.
	 * 
	 * @param context Context The context of the view where this method is called.
	 * @return ComponentName Return informations about the current top view.
	 */
    public ComponentName getTopComponent(Context context) {

        try {
            ActivityManager activityManager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
            List<RunningTaskInfo> localList = activityManager.getRunningTasks(1);

            if ((localList == null) || (localList.isEmpty())) {
                return null;
            }

            ComponentName localComponentName = ((ActivityManager.RunningTaskInfo) localList.get(0)).topActivity;

            return localComponentName;

        } catch (Exception e) {
        	Log.e(Constants.APP_NAME, e.toString());
            return null;
        }
    }
    
    /**
     * This method is used to handle message receiving.
     * If you are currently on the conversation of the message's author just refresh the view,
     * otherwise display a notification.
     * 
     * @param context Context Context of the application.
     * @param message Information about the message just received.
     */
	public void receiveMessage(Context context, Message message){
		Friend author = new Friend(message.getAuthor());
		ChatConversation convers;
		ComponentName localComponentName = getTopComponent(context);
		
		if(isNewConversation(author)){
			convers = newConversation(author);
		}else{
			convers = getConversationByUser(author);
		}
		convers.addMessage(message);
		if(localComponentName.getClassName().equals(Constants.PACKAGE_NAME+".gui.ChattingActivity")
				&& convers.isActive()){
			
			/* Thread to refresh the view 
			 * because the user is currently on the good conversation */
			Runnable run = new Runnable() {			
				@Override
				public void run() {
					ChattingActivity.getInstance().update();				
				}
			};
			ChattingActivity.getInstance().runOnUiThread(run);
		}else{
			/* Notification sending because the user is not
			 * on the message's author conversation. */
			
			Intent intent = new Intent(context, ChattingActivity.class);
			intent.putExtra(Friend.KIND, new Friend(message.getAuthor()));
			PendingIntent pi = PendingIntent.getActivity(context, 0, intent, 0);
			
			Notification noti = new Notification(R.drawable.ic_launcher, context.getResources().getString(R.string.message_received)+ " " + message.getAuthor()
					, System.currentTimeMillis());
			noti.setLatestEventInfo(context, context.getResources().getString(R.string.message_received)+ " " + message.getAuthor() ,
					message.getContent(), pi);
			notify(context, noti);
		}	
		
	}
	
	/**
	 * This method is used to send a message to a user.
	 * 
	 * @param context Context Context of the view where this method is called.
	 * @param message Message Message you want to send to someone. 
	 * @return JSONObject A JSONObject to know if the message sending was a success or not, used to display error message.
	 */
	public JSONObject sendMessage(Context context, Message message){
		return message.send(context);
	}
	
	/**
	 * Method used to be registered on the cloud application.
	 * 
	 * @param context Context Context of the view where this method is called.
	 * @param regInfo RegistrationInfo Registration informations you want to send to the server.
	 * @return int Return an int to handle error message.
	 */
	public int register(Context context, RegistrationInfo regInfo){
		return regInfo.send(context);
	}
	
	/**
	 * Method used to be authenticate by the cloud application.
	 * 
	 * @param context Context Context of the view where this method is called.
	 * @param autInfo
	 * @return JSONObject JSONObject return to handle message error.
	 */
	public JSONObject authentication(Context context, AuthenticationInfo autInfo){
		return autInfo.send(context);
	}

	/**
	 * Method used to get the regId store in the controller.
	 * 
	 * @return String return the String version the regID.
	 */
	public String getRegId() {
		return regId;
	}

	/**
	 * Method used to set the regID in the controller.
	 * 
	 * @param regId String The regId.
	 */
	public void setRegId(String regId) {
		this.regId = regId;
	}
	
	/**
	 * This method is used to store the sessionId and informations
	 * of the current user in the SharedPreferences of the application.
	 * 
	 * @param context Context Context of the view where this method is called.
	 * @param sessionId String The session Id of the user using the application.
	 * @param user JSONObject User informations.
	 */
	public void initSessionId(Context context, String sessionId, JSONObject user){
		SharedPreferences settings = context.getSharedPreferences(Constants.PREFS_NAME, 0);
		SharedPreferences.Editor editor = settings.edit();
		editor.putString(Constants.SESSIONID_NAME, sessionId);
		try {
			editor.putString(User.USERNAME_NAME, user.getString(User.USERNAME_NAME));
			editor.putString(User.FIRSTNAME_NAME, user.getString(User.FIRSTNAME_NAME));
			editor.putString(User.SURNAME_NAME, user.getString(User.SURNAME_NAME));
			editor.putString(User.EMAIL_NAME, user.getString(User.EMAIL_NAME));
			editor.putInt(User.SEX_NAME, user.getInt(User.SEX_NAME));
			editor.putBoolean(User.SHOWREALNAME_NAME, user.getBoolean(User.SHOWREALNAME_NAME));
			editor.putBoolean(User.AVAILABLE_NAME, true);
		} catch (Exception e) {
			Log.e(Constants.APP_NAME, "Error to store user's data : " + e.toString());
		}

		editor.commit();
		Log.i(Constants.APP_NAME, "Session Id store");
	}
	
	/**
	 * This method is used to get session informations store in the Preferences of the application.
	 * 
	 * @param context Context Context of the view where this method is called.
	 * @return SimpleEntry<String, JSONObject> Session informations and current users.
	 */
	public SimpleEntry<String, JSONObject> getSessionInfo(Context context){
		SharedPreferences settings = context.getSharedPreferences(Constants.PREFS_NAME, 0);
		String sessionId = settings.getString(Constants.SESSIONID_NAME, "");
		JSONObject user = new JSONObject();
		try {
			user.put(User.USERNAME_NAME, settings.getString(User.USERNAME_NAME,""));
			user.put(User.FIRSTNAME_NAME, settings.getString(User.FIRSTNAME_NAME,""));
			user.put(User.SURNAME_NAME, settings.getString(User.SURNAME_NAME,""));
			user.put(User.SEX_NAME, settings.getInt(User.SEX_NAME,User.SEX_UNKNOWN));
			user.put(User.EMAIL_NAME,settings.getString(User.EMAIL_NAME, ""));
			user.put(User.SHOWREALNAME_NAME, settings.getBoolean(User.SHOWREALNAME_NAME,false));
			user.put(User.AVAILABLE_NAME,settings.getBoolean(User.AVAILABLE_NAME,true));
		} catch (Exception e) {
			Log.e(Constants.APP_NAME, "Error to retrieve user's data : " + e.toString());
		}
		SimpleEntry<String, JSONObject> info = new SimpleEntry<String, JSONObject>(sessionId, user);
		return info;
	}
	
	/**
	 * This method is used to know if current session informations are still valid.
	 * 
	 * @param context Context Context of the view where this method is called.
	 * @param sessionId String sessionId of the current user.
	 * @param username String username of the current user.
	 * @param regId String regId of the current user.
	 * @return JSONObject Information from the server to know if session is valid or not.
	 */
	public JSONObject getSessionInfoFromServer(Context context,String sessionId, String username, String regId){	
		HttpService service = new HttpService(context);
		return service.getSessionInformations(sessionId, username, regId);	
	}
	
	/** 
	 * This method is a called used to know if session informations are valid and
	 * handle the response.
	 * 
	 * @param context Context Context of the view where this method is called.
	 * @param dir Class name of the class to redirect if session is valid.
	 * @return boolean return true if session is valid, otherwise return false.
	 */
	public boolean checkSession(final Context context, final Class<?> dir){
		final SimpleEntry<String,JSONObject> info = getSessionInfo(context);
		boolean returnValue;
		if(!info.getKey().equals("")){
			returnValue=true;
			AsyncTask<Void, Void, JSONObject> task = new AsyncTask<Void, Void, JSONObject>() {
				
				@Override
				protected JSONObject doInBackground(Void... params) {
					String username="";
					try {
						username=info.getValue().getString(User.USERNAME_NAME);
					} catch (Exception e) {
						
					}
					return controller.getSessionInfoFromServer(context,info.getKey()
							, username, controller.getRegId());
				}
				
				@Override
				protected void onPostExecute(JSONObject result) {
					super.onPostExecute(result);
					int code;
					try {
						code = result.getInt(Constants.CODE_NAME);
					} catch (Exception e) {
						code = Constants.CODE_ERROR;
						Log.e(Constants.APP_NAME, "Error to retrieve session informations : " + e.toString());
					}
					
					if(Constants.CODE_SESSION_VALID==code){
						Intent i = new Intent(context, dir);
						i.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
						context.startActivity(i);							
					}else{
						Intent i = new Intent(context, LoginActivity.class);
						context.startActivity(i);
					}
				}			
			};
			
			if(Utils.hasConnectivity(context)){
				task.execute();
			}else{
				Toast.makeText(context, context.getResources().getString(R.string.no_connectivity)
						, Toast.LENGTH_LONG).show();
			}
		}else{
			returnValue=false;
		}
		return returnValue;
	}
	
	/**
	 * This method is used to get the list of users from the cloud application.
	 * 
	 * @param context Context Context of the view where this method is called.
	 * @param info Filter informations to research users
	 * @return JSONObject List of users retrieve from the cloud application and error message.
	 * 
	 */
	public JSONObject getConnectedUsers(Context context,final JSONObject info){
		HttpService service = new HttpService(context);
		
		SimpleEntry<String,JSONObject> prefs = getSessionInfo(context);
		try {
			info.put(Constants.SESSIONID_NAME, prefs.getKey());
			info.put(User.USERNAME_NAME, prefs.getValue().getString(User.USERNAME_NAME));
		} catch (Exception e) {
			return null;
		}
		return service.getConnectedUsers(info);
	}
	
	/**
	 * This method return the friendlist, the blacklist and the waiting list from the cloud application.
	 * 
	 * @param context Context Context of the view where this method is called.
	 * @return JSONObject Return the friendlist, the blacklist and the waiting list from the cloud application store as JSONArray.
	 */ 
	public JSONObject getLists(Context context){
		HttpService service = new HttpService(context);	
		SimpleEntry<String,JSONObject> prefs = getSessionInfo(context);
		String username="";
		try {
			username = prefs.getValue().getString(User.USERNAME_NAME);
		} catch (Exception e) {
		}
		
		return service.getLists(prefs.getKey(), username);
	}
	
	/**
	 * 
	 * @param context Context Context of the view where this method is called.
	 * @param info JSONObject Targeted user and action to do on it.
	 * @return JSONObject Return informations about the managing of the user, error code,...
	 */
	public JSONObject manageFriend(Context context, JSONObject info){
		HttpService service = new HttpService(context);
		
		SimpleEntry<String,JSONObject> prefs = getSessionInfo(context);
		try {
			info.put(Constants.SESSIONID_NAME, prefs.getKey());
			info.put(User.USERNAME_NAME, prefs.getValue().getString(User.USERNAME_NAME));
		} catch (Exception e) {
			return null;
		}
		return service.manageFriend(info);
	}
	
	/**
	 * This method is used to update the current location of the user.
	 * 
	 * @param context Context Context of the view where this method is called.
	 * @param latitude double The current latitude of the user
	 * @param longitude The current longitude of the user
	 */
	public void updateLocation(Context context, double latitude, double longitude){
		HttpService service = new HttpService(context);
		JSONObject location = new JSONObject();
		
		SimpleEntry<String,JSONObject> prefs = getSessionInfo(context);
		if(!prefs.getKey().isEmpty()){
			try {
				location.put(Constants.SESSIONID_NAME, prefs.getKey());
				location.put(User.USERNAME_NAME, prefs.getValue().getString(User.USERNAME_NAME));
				location.put(User.LATITUDE_NAME, latitude);
				location.put(User.LONGITUDE_NAME, longitude);
			} catch (Exception e) {
				Log.e(Constants.APP_NAME, "Probleme to generate location JSONObject : " + e.toString());
			}
			
			service.updateLocation(location);
		}	
	}
	
	/**
	 * This method is used to tell to the server to set this current user as active.
	 * 
	 * @param context Context Context of the view where this method is called.
	 * @param stayActive boolean The value to stay active (true) or not (false).
	 */
	public void stayActive(Context context, boolean stayActive){
		HttpService service = new HttpService(context);	
		SimpleEntry<String,JSONObject> prefs = getSessionInfo(context);
		String username="";
		try {
			username = prefs.getValue().getString(User.USERNAME_NAME);
		} catch (Exception e) {
		}
		
		service.stayActive(prefs.getKey(), username, stayActive);
	}
	
	/**
	 * This method is used to increase the notification number and display the notification.
	 * 
	 * @param context Context Context of the view where this method is called.
	 * @param noti Notification The notification to display.
	 */
	public void notify(Context context, Notification noti){
		NotificationManager mg = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
		
		noti.vibrate = new long[]{100, 200, 100, 500}; 
		noti.number=notifId++;
		noti.flags |= Notification.FLAG_AUTO_CANCEL;
		mg.notify(notifId, noti);
	}
	
	/**
	 * This method is used to disconnect the user by erasing the session informations.
	 * 
	 * @param context Context Context of the view where this method is called.
	 */
	public void disconnect(Context context){
		SharedPreferences settings = context.getSharedPreferences(Constants.PREFS_NAME, 0);
		SharedPreferences.Editor editor = settings.edit();
		editor.clear();
		editor.apply();
		
		Intent intent = new Intent(context, LoginActivity.class);
		intent.putExtra("from", context.getClass().getName());
		context.startActivity(intent);
	}
	
	/**
	 * Method to get all the interests of someone.
	 * 
	 * @param context Context Context of the view where this method is called.
	 * @param listOwnerName String name of the user who wants to know the interests
	 * @return JSONObject Interests and code return buy the request
	 */
	public JSONObject getLinkedInterests(Context context, String listOwnerName){
		HttpService service = new HttpService(context);
		SimpleEntry<String,JSONObject> prefs = getSessionInfo(context);
		String username="";
		try {
			username = prefs.getValue().getString(User.USERNAME_NAME);
		} catch (Exception e) {
		}
		return service.getInterests(prefs.getKey(),username,listOwnerName,true);
	}
	
	/**
	 * Method to get all the interests the user doesn't have.
	 * 
	 * @param context Context Context of the view where this method is called.
	 * @param listOwnerName String name of the user who wants to know the interests 
	 * @return JSONObject Interests and code return buy the request
	 */
	public JSONObject getUnlinkedInterests(Context context, String listOwnerName){
		HttpService service = new HttpService(context);
		SimpleEntry<String,JSONObject> prefs = getSessionInfo(context);
		String username="";
		try {
			username = prefs.getValue().getString(User.USERNAME_NAME);
		} catch (Exception e) {
		}
		return service.getInterests(prefs.getKey(),username,listOwnerName,false);
	}
	
	/**
	 * This method is used to manage your interests ie adding, deleting.
	 * 
	 * @param context Context Context of the view where this method is called.
	 * @param info JSONObject Informations to manage Interest
	 * @return JSONObject Return code buy the request.
	 */
	public JSONObject manageInterests(Context context, JSONObject info){
		HttpService service = new HttpService(context);
		SimpleEntry<String,JSONObject> prefs = getSessionInfo(context);
		try {
			info.put(Constants.SESSIONID_NAME, prefs.getKey());
			info.put(User.USERNAME_NAME, prefs.getValue().getString(User.USERNAME_NAME));
		} catch (Exception e) {
			Log.e(Constants.APP_NAME, "Error to put information in JSONObject when managing interests : " + e.toString());
		}
		return service.updateInterests(info);
	}
	
	/**
	 * 
	 * @param context Context Context of the view where this method is called.
	 * @param user User User's informations you want to update.
	 * @return JSONObject Return code buy the request.
	 */
	public JSONObject updateUserInformations(Context context, JSONObject user){
		JSONObject info = new JSONObject();
		SimpleEntry<String,JSONObject> prefs = getSessionInfo(context);
		try {
			info.put(Constants.SESSIONID_NAME, prefs.getKey());
			info.put(User.USERNAME_NAME, prefs.getValue().getString(User.USERNAME_NAME));
			info.put(User.KIND,user);
		} catch (Exception e) {
			Log.e(Constants.APP_NAME, "Error to create JSON info to update user : " + e.toString());
		}
		HttpService service = new HttpService(context);
		return service.updateUserInformations(info);
	}
}
